Notas sobre el manejo de la opacity al determinar el Color Contrast Ratio con herramientas online
En este documento se aborda el tema de cómo calcular el CC ratio cuando existen elementos con opacidad menor que 1, ya que durante el desarrollo del detector, hemos identificado una posible mejora en este proceso para futuras auditorías. El documento trata algunos temas técnicos sobre colores que se intentan ejemplificar y explicar de forma que sean entendibles por cualquier lector.
Comenzamos con algunas definiciones que facilitarán la escritura del resto del documento.
Definiciones
En esta sección definimos los conceptos de opacity y alpha-composition
Opacity: Define cuán opaco es un elemento en la página. Un elemento con opacity = 1 será totalmente opaco. Esto implica que el elemento "tapa" completamente lo que tenga detrás. No obstante, podemos encontrar elementos con opacity < 1 lo que implica que el elemento "deja ver" en cierta medida lo que tiene detrás. Si la opacity es cero, el elemento será totalmente transparente y podrá verse perfectamente lo que tiene detrás. Los valores de la opacity van de 0 a 1, siendo 0 totalmente transparente y 1 totalmente opaco. En lo adelante usaremos el término elemento no opaco para referirnos a elementos con opacity < 1.
La opacity puede estar incluida en el color de background o foreground de un elemento. Por ejemplo, el color rgba(255, 0, 0, 0.5) es un rojo con opacity = 0.5. A este formato de color le llamamos rgba, ya que incluye los clásicos valores de red, green y blue para definir un color pero además añade el valor alpha que representa la opacity. Es frecuente usar el término alpha para referirse a la opacity como veremos más adelante.
La opacity también puede estar definida con la propiedad CSS opacity.
Alpha-composition: Como ya vimos en la definición anterior, un elemento no opaco deja ver, en mayor o menor medida, los elementos que están detrás de él. Por ejemplo, si un elemento tiene un background rojo pero con opacidad menor que 1, digamos rgba(255, 0, 0, 0.5), y detrás tiene un elemento totalmente blanco, entonces se produce una mezcla entre el rojo no opaco y el blanco de detrás. El usuario termina percibiendo un color más parecido al rosado debido a la mezcla entre el rojo y el blanco. Si el valor de la opacity sube y acerca a 1 el usuario percibirá un color más rojizo y si la opacity baja y se acerca a cero el usuario percibirá un color tirando más al blanco. ¿Pero qué color es exactamente el que percibimos, cuál es su código rgb o hexadecimal?
En este caso estamos mezclando un rojo con alpha = 0.5, o sea rgba(255, 0, 0, 0.5) con un blanco rgba(255, 255, 255, 1). Con estos datos es posible aplicar una fórmula para obtener exactamente el color resultante. Esto es lo que se conoce como alpha-composition. A continuación describimos el proces de alpha composition para estos dos colores, pero es totalmente opcional para comprender el resto del documento.
Alpha composition en acción (opcional): Lo primero es hacer que los valores de los colores estén entre 0 y 1, esto lo logramos dividiendo cada canarl (red, green, blue) por 255. Los colores se convierten entonces en rgba(1, 0, 0, 0.5) y rgba(1, 1, 1, 1). Luego necesitamos calcular el alpha resultante de la combinación con la fórmula: $\alpha_r = \alpha_f + \alpha_b(1 - \alpha_f)$ donde $\alpha_r$ es el alpha resultante, $\alpha_f$ es el alpha del foreground (0.5) y $\alpha_b$ el del background (1).
Usando los valores de nuestro ejemplo: $\alpha_r = 0.5 + 1(1 - 0.5) = 1$. Lo que significa que nuestro alpha resultante será 1. Ahora tenemos que modificar cada canal de color como sigue: $C_r = \frac{C_f\alpha_f + C_b\alpha_b(1 - \alpha_f)}{\alpha_r}$. Donde $C_r$, $C_f$, y $C_b$ es el canal resultante, de foreground y de background respectivamente. Si hacemos esto para cada canal (red, green, y blue), obtendremos el color final:
Red: $C_r = \frac{1 \times 0.5 + 1 \times 1\times 0.5}{1} = 1$
Green: $C_r = \frac{0 \times 0.5 + 1 \times 1 \times 0.5}{1} = 0.5$
Blue: $C_r = \frac{0 \times 0.5 + 1 \times 1 \times 0.5}{1} = 0.5$
Ahora cada canal lo multiplicamos por 255 y lo redondeamos con lo que nos queda el color rgba(255, 128, 128, 1) como resultado. Como se puede observar, este es un color rosáceo.
A continuación exponemos cómo se calculan los colores para el CC ratio en el detector
Manejo de la opacidad para el CC ratio en el detector
Para calcular el CC ratio en cualquier escenario son necesarios el color de background y el de foreground. En el caso del detector es sumamente importante la diferencia entre background y foreground ya que cuando los elementos no son opacos, ambos colores se calculan de manera diferente. Como veremos luego, esta es una de las claves en la diferencia entre el CC ratio que calculamos en el detector y el que se calcula en las herramientas online.
Tomemos el siguiente ejemplo: el texto Foreground es totalmente negro y opaco (rgba(0, 0, 0, 1)). El background azul es rgba(165, 216, 255, 0.5) o #A5D8FF con opacity 0.5 y el elemento Container que contiene todo es totalmente blanco (rgba(255, 255, 255, 1)).
Para calcular el color de foreground en el detector hacemos el alpha-composition entre el color de foreground y el de background del mismo elemento. O sea, mezclamos el negro totalmente opaco del texto Foreground con el background no opaco. Como el color del Foreground es totalmente opaco, no se produce ninguna mezcla y el color resultante es negro (rgba(0, 0, 0, 1)).
Para calular el color del background no opaco hacemos el alpha-composition entre el background del elemento y su elemento contenedor, resultando en un color más claro que el azul inicial. Si aplicamos la fórmula de la sección anterior obtenemos el color rgba(210, 236, 255, 1) o #D2ECFF. Si usamos el detector de color del Paint vemos que nos da un color muy parecido con una pequeña diferencia debido al redondeo y al renderizado.
Luego, si usamos estos dos colores (rgba(0, 0, 0, 1) y rgba(210, 236, 255, 1)) en una herramienta de CC ratio, el ratio resultante queda holgadamente por encima del umbral.
Cuando la opacity está en el propio elemento
La regla anterior —foreground mezclado sobre su propio background, background mezclado sobre su contenedor— describe un alpha que viaja dentro del valor de color (rgba(...)). La propiedad CSS opacity se comporta de otra forma, y el detector la trata como un caso aparte.
Una opacity menor que 1 no atenúa el texto y la superficie por separado. Aplana todo el elemento —su background y sus glifos— en una sola capa, y solo después compone esa capa sobre lo que tenga detrás. Mezclar el texto a través del background del propio elemento, como en el caso base, metería ese background dentro de los glifos por segunda vez y reportaría un color que la pantalla nunca muestra.
El detector reconoce esta situación por su firma: tanto el foreground como el background terminan con un alpha menor que 1, y ambos alphas son prácticamente iguales (difieren en menos de 0.05). Cuando eso ocurre:
- toma una captura pequeña del elemento y extrae con cuentagotas el píxel visible del background: ese píxel ya es la composición, así que no hace falta ninguna fórmula;
- deriva el color visible del texto a partir de ese mismo fondo y ese mismo alpha, en lugar de mezclarlo a través del background del elemento.
Ejemplo: un botón verde con background: rgb(38, 180, 74); color: #fff y :hover { opacity: .85 }. Mezclar el texto blanco a través del verde daría un #def0e3 verdoso; la pantalla en realidad pinta un gris claro #dddede, y ese es el valor que reporta el detector.
Qué muestra el reporte
Como los números y los colores de un hallazgo tienen que ser coherentes entre sí, el reporte nunca muestra un valor de 8 dígitos por sí solo:
- El background se muestra ya compuesto. Un background translúcido aparece con el color al que se resuelve sobre su fondo, que es el mismo color contra el que se midió el ratio.
- El foreground se aplana igual. Un color de texto translúcido aparece con el color visible ya mezclado, para que su nombre de color (por ejemplo "gris" en vez de "negro") coincida con lo que ve quien lee.
- El valor declarado no se oculta. Cuando un color se declaró con canal alpha, el reporte lo escribe como
approximate #RRGGBB (real value: #RRGGBBAA), de modo que una persona desarrolladora aún pueda localizar la declaración en la hoja de estilos. - Un color leído de la pantalla se etiqueta como tal. Cuando la superficie hubo que extraerla con cuentagotas en vez de derivarla del CSS, el reporte la describe como
sampled <color> background, approx (#hex), con el mismo marcadorapproxque se usa para gradientes e imágenes de fondo.
Las sugerencias de corrección también se calculan a partir de estos colores visibles y aplanados, así que un valor sugerido se puede aplicar y volver a medir directamente.
A continuación exponemos cómo se utiliza actualmente la herramienta de CC ratio y veremos que los resultados discrepan con el proceso anterior.
Cálculo directo de CC ratio en auditorías
Usando el mismo ejemplo anterior, tenemos un foreground negro y un background no opaco. Pero en la mayoría de las herramientas online que se usan en las auditorías no se permite ingresar un background no opaco, y aunque se permitiera, la herramienta no tiene información sobre el color que hay por detrás de ese background. Por tanto, lo que se haría en estos casos es usar el color de background como foreground y viceversa. Cuando los colores son totalmente opacos, cambiar background y foreground no afecta el resultado del CC ratio, pero cuando entran en juego colores no opacos, sí hay una diferencia.
En el caso del ejemplo anterior, un auditor usaría directamente el color del background #A5D8FF y alpha 0.5 como foreground y el color negro como background.
El CC ratio resultante es totalmente diferente. Incluso se reporta una violación de CC cuando en realidad el contraste es más que suficiente si tomamos en cuenta lo que realmente percibe el usuario. Si analizamos los ejemplos que nos da la herramienta nos damos cuenta qué es lo que está pasando.
La herramienta asume que literalmente nuestro background es negro y que nuestro foreground es color azulado no opaco. Entonces el alpha-composition se realiza entre el color azul y el negro. Por lo que la luminosidad y finalmente el CC ratio son muy distintos al del escenario real.
Conclusiones
La diferencia en la metodología al calcular CC ratios entre el detector y las auditorías manuales puede resultar en grandes discrepancias en el resultado final e incluso en diferencias en el reporte final de issues.
Este documento fue redactado por un desarrollador que no conoce a fondo los detalles de las auditorías y cómo el equipo de a11y calcula el contraste de color. Está basado en reportes de error del proceso de QA del detector. No obstante, si se comprueba que en efecto la metodología expuesta de cálculo de CC ratio es la que se usa en las auditorías, se recomienda tener conversaciones para determinar si el detector debe adaptarse a esta metodología o si la metodología debe adaptarse al enfoque del detector.
Mientras tanto, las reglas de reporte descritas arriba son las que mantienen conciliables ambas lecturas: el hallazgo muestra el color compuesto que midió el detector y el valor declarado con su canal alpha, así que quien revisa puede reproducir cualquiera de los dos cálculos sin adivinar cuál produjo el ratio reportado.
Anexo
Conversación con ChatGPT al respecto (la respuesta del alliBot a la misma pregunta fue muy similar) https://chatgpt.com/share/685146f0-fbf4-8001-b3ef-1117e8b61922
